using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using DuinoWebserver.Core.Helpers;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.Net.NetworkInformation;
using SecretLabs.NETMF.Hardware.Netduino;
using Tswe.NETMF.Net;
using System.IO;
using System.IO.Ports;
using System.Text;
using DuinoWebserver.NetworkTime;

namespace DuinoWebserver
{
    public class Application
    {
#if EMULATOR
        private const string WEB_FOLDER = @"\WinFS"; // use WinFS for Emulator
#else
        private const string WEB_FOLDER = @"\SD";
#endif
        private UdpSocket _udpServer;
        private WebServer _webServer;

        public InputPort Switch = new InputPort(Pins.ONBOARD_SW1, false, Port.ResistorMode.Disabled);
        public OutputPort Led = new OutputPort(Pins.ONBOARD_LED, false);

        private bool _ledStatus = true;

        public void Init()
        {
            initDebug();

            initNetwork();
            initDiscovery();
            initWebServer();
            initTimeSet();
        }

        #region SerialDebug

#if SerialDebug
        private static SerialPort _debugPort;
#endif

        public static void DebugPrint(string text)
        {
#if SerialDebug
		    var bytesToSend = UTF8Encoding.UTF8.GetBytes(text + "\r\n");
            _debugPort.Write(bytesToSend, 0, bytesToSend.Length);
#else
            Debug.Print(text);
#endif
        }

        private void initDebug()
        {
#if SerialDebug
            _debugPort = new SerialPort(SerialPorts.COM2, (int)BaudRates.Baud115200, Parity.None, 8, StopBits.One);
            _debugPort.Handshake = Handshake.None;
            _debugPort.ReadTimeout = 500;
            _debugPort.Open();
#endif
        }

        #endregion

        public void Loop()
        {
            Led.Write(_ledStatus);
            Thread.Sleep(50);
        }

        public void Dispose()
        {
            _udpServer.Stop();
            _webServer.Stop();
        }

        #region WebServer

        void initTimeSet()
        {
            new Thread(() =>
            {
                 string[] server = { "time.windows.com", "time-a.nist.gov"};
                DateTime dtntp;
                for (int i = 0; i < server.Length; i++)
                {
                    if (NtpClient.TryGetNetworkTime(server[i], out dtntp))
                    {
                        Debug.Print("success NTP: " + dtntp);
                        Utility.SetLocalTime(dtntp);
                        break;
                    }
                    else
                        Debug.Print("error NTP: " + dtntp);
                }
             }).Start();
        }

        private void initWebServer()
        {
            _webServer = new WebServer(80) { WebFolder = getContentPath() };
            _webServer.AddPathHandler("/index.html", HttpRequestMethod.Get, indexHandler);
            _webServer.AddPathHandler("/ledstatus*", HttpRequestMethod.Get, ledStatusHandler);
            _webServer.AddPathHandler("/blinkled", HttpRequestMethod.Post, blinkLedHandler);
            _webServer.AddPathHandler("/web/js/led.js", HttpRequestMethod.Get, ledJsonHandler);
            
            _webServer.AddDefaultPathHandlers();
        }

        private static string getContentPath()
        {
#if MF_FRAMEWORK
            return WEB_FOLDER;
#else
            var result = Path.Combine(Environment.CurrentDirectory, @"..\..\www");
            result = Path.GetFullPath(result);
            return result;
#endif
        }

        private void ledJsonHandler(Request request)
        {
            _GC();
            var status = _ledStatus ? "1" : "0";

            var json = new Hashtable();
            json.Add("LedStatus", _ledStatus);
            json.Add("SwitchStatus", Switch.Read());
            json.Add("Status", status);

            var jsonString = JSON.JsonEncode(json);
            request.SendResponse(jsonString, "text/js");
            _GC();
        }

        private void indexHandler(Request request)
        {
            request.SendResponse("<html><body><p>Request from "
                + request.ClientIPAddress.ToString()
                + " received at " + DateTime.Now.ToString()
                + "</p><p>Method: " + request.Method.ToString()
                + "<br />URL: " + request.Url +
                "</p></body></html>");
        }

        private void blinkLedHandler(Request request)
        {
            var data = request.ReceiveData();
            if (data == null || data.Length == 0) return;
            
            _ledStatus = data[0] != '0';

            // send new state response (not used)
            ledStatusHandler(request);
        }

        private void ledStatusHandler(Request request)
        {
            var status = _ledStatus ? "1" : "0";
            request.SendResponse(status, "text/plain");
        }

        #endregion
        
        private void _GC(bool printSize = true)
        {
            var size = Microsoft.SPOT.Debug.GC(true);
            if (printSize)
                DebugPrint("GC free size: " + size.ToString());
        }

        private void initNetwork()
        {
            Microsoft.SPOT.Debug.EnableGCMessages(false);
            NetworkInterface[] networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();

            // this changes static IP in Netduino flash memory (do this only once)
            // http://forums.netduino.com/index.php?/topic/1282-change-static-ip-from-code/
            //
            var setStaticIP = false;
            if (setStaticIP)
            {
                var nif = networkInterfaces[0];
                nif.EnableStaticIP("192.168.2.111", "255.255.255.0", "192.168.2.1");
            }

            // print current network settings
            foreach (var nic in networkInterfaces)
            {
                DebugPrint("IP-Address: " + nic.IPAddress);
                DebugPrint("Subnet Mask: " + nic.SubnetMask);
                DebugPrint("Gateway-Address: " + nic.GatewayAddress);
            }
        }

        #region Discovery

        private string _remoteUrl;

        private void initDiscovery()
        {
            _udpServer = new UdpSocket
            {
                LocalPort = 8221,
                Broadcast = true
            };
            _udpServer.DataReceived +=
                new DataReceivedHandler(udpServer_DataReceived);
            _udpServer.Start();
        }

        public class ResponseMessage
        {
            public string Prefix { get; set; }
            public string Description { get; set; }
            public string IPAddress { get; set; }
            public int IPPort { get; set; }
        }

        void udpServer_DataReceived(object sender, DataReceivedEventArgs e)
        {
            var ep = (IPEndPoint)e.RemoteEndPoint;
            var ipAddress = ep.Address;
            var ipPort = ep.Port;

            _remoteUrl = ipAddress.ToString();
            DebugPrint("USID from : " + _remoteUrl
                + " Port :" + ipPort.ToString() + " received.");

            string prefix = "http://" + _udpServer.IPAddress + ":8221/web/";
            var responseMessage = new ResponseMessage
            {
                Prefix = prefix,
                Description = "Netduino Plus 2",
                IPAddress = _udpServer.IPAddress,
                IPPort = _webServer.portNumber // 8221
            };

            sendResponse(ep, responseMessage);
        }

        private void sendResponse(IPEndPoint endpoint, ResponseMessage message)
        {
            byte[] buffer = SerializerHelpers.Serialize(message);
            try
            {
                _udpServer.SendTo(endpoint, buffer);
                DebugPrint("Message sent to: " + endpoint.ToString());
            }
            catch (SocketException se)
            {
                DebugPrint("UDP Server exception: Code = " + se.ErrorCode.ToString());
            }
        }

        #endregion
    }
}
